package org.semanticflash.rdf.io.ntriple
{
	
	import com.bigflexlib.utils.AsyncRunner;
	import com.bigflexlib.x.mx.logging.Log.LogUtil;
	
	import mx.logging.ILogger;
	
	import org.semanticflash.rdf.io.AbstractRDFParser;
	import org.semanticflash.rdf.io.IRDFHandler;
	import org.semanticflash.rdf.values.IRI;
	import org.semanticflash.rdf.values.RDFLangLiteral;
	import org.semanticflash.rdf.values.RDFPlainLiteral;
	import org.semanticflash.rdf.values.RDFTypedLiteral;
	import org.semanticflash.rdf.values.RDFValue;



	/**
	 * 
	 * TODO: prefixes
	 * 
	 * @author aleyton@gmail.com
	 * 
	 */
	public class NTripleParser extends AbstractRDFParser
	{
		
		
		private var logger:ILogger;
		



		public function NTripleParser( input:*, rdfHandler:IRDFHandler, baseURL:String = null  )
		{
			logger = LogUtil.getStandardLogger( this );
			super( input, rdfHandler, baseURL );
		}





		override protected function start(  ):void
		{
			
			var lines:Array;
			
			//In case we have \r or \n
			if( input.indexOf( "\r" ) == -1 )
				lines = input.split( "\n" );
			else if( input.indexOf( "\n" ) == -1 ) 
				lines = input.split( "\r" );
			else
				lines = input.split( "\r" ).join( "\n" ).split( "\n" );
			
			for each ( var line:String in lines )
				AsyncRunner.queue( parseLine, [ line ] );
			
			
		}
		
		
		
		
		private function parseLine( line:String ):void
		{
			
			if( line.length == 0 )
				return;
			
			var firstToLook:int = getIndexOfFirstNonWS( line );
			
			if( firstToLook == line.length )
				return;
			
			// if ( Log.isDebug() ) logger.debug( "parseLine: " + line );
			
			
			var c:String;
			c = line.charAt( firstToLook );
			
			
			if( c == "#" )
			{
				//Ignore comment
				return;
			}
			else
			{
				parseStatementLine( line.substring( firstToLook ) );
			}
			
				
		}
		
		
		
		private function parseStatementLine( line:String ):void
		{
			if( line.indexOf( " " ) == -1 )
			{
				throw new Error( "N-Triple syntax error: missing ' ' after subject." );
			}
			var subjectString:String = line.substring( 0, line.indexOf( " " ) );
			
			//To simplify, the subject is removed from the 'line'
			line = line.substring( line.indexOf( " " ) );
			//And let's eliminate the white spaces
			line = line.substring( getIndexOfFirstNonWS( line ) );
			
			if( line.indexOf( " " ) == -1 )
			{
				throw new Error( "N-Triple syntax error: missing ' ' after predicate." );
			}
			
			var predicateString:String = line.substring( 0, line.indexOf( " " ) );
			
			//The predicate is removed from the 'line'
			line = line.substring( line.indexOf( " " ) );
			//And let's eliminate the white spaces
			line = line.substring( getIndexOfFirstNonWS( line ) );
			
			if( line.indexOf( "." ) == -1 )
			{
				throw new Error( "N-Triple syntax error: missing '.' after object." );
			}
			
			var objectString:String = line.substring( 0, getIndexOfLastNonWSBeforeDot( line ) + 1 );
			
			var subject:IRI = parseSubject( subjectString ); 
			
			var predicate:IRI = parsePredicate( predicateString );
			
			var object:RDFValue = parseObject( objectString );
			
			assertStatement( subject, predicate, object );
			
		}
		
		
		////////////////////////////////////////////////////////////////////////////////////////
		//////////////////////////////////////// Parsers ///////////////////////////////////////
		////////////////////////////////////////////////////////////////////////////////////////
		
		internal static function parseSubject( line:String ):IRI
		{
			var c:String = line.charAt( 0 );
			//Two cases
			if( c == "<" )
			{
				//iri
				return findIRIInBrackets( line );
			}
			else if( c == "_" )
			{
				//bnode
				c = line.charAt( 1 );
				if( c != ":" )
				{
					throw new Error( "N-Triple syntax error: expected ':' and found " + c );
				}
				else
				{
					return IRI.bNode();
				}
			}
			else
			{
				throw new Error( "N-Triple syntax error: expected '_' or '<' and found " + c );
			}
		}
		
		internal static function parsePredicate( line:String ):IRI
		{
			var c:String = line.charAt( 0 );
			if( c == "<" )
			{
				//iri
				return findIRIInBrackets( line );
			}
			else
			{
				throw new Error( "N-Triple syntax error: expected '<' and found " + c );
			}
		}
		
		internal static function parseObject( line:String ):RDFValue
		{
			var value:String;
			var lang:String;
			var datatype:IRI;
			
			var c:String = line.charAt( 0 );
			//Three cases
			if( c == "<" )
			{
				//iri
				return findIRIInBrackets( line );				
			}
			else if( c == "_" )
			{
				//bnode
				c = line.charAt( 1 );
				if( c != ":" )
				{
					throw new Error( "N-Triple syntax error: expected ':' and found " + c );
				}
				else
				{
					return IRI.bNode( ); // we need to keep an internal dict of the current bnodes
				}
			}
			else if( c == "\"" )
			{
				if( line.substring( 1 ).indexOf( "\"" ) == -1 )
				{
					throw new Error( "N-Triple syntax error: missing '\"'" );
				} 
				else
				{
					value = line.substring( 1, line.lastIndexOf( "\"" ) );
				}
				
				//We have to check if this is a lang literal or a typed literal 
				var isTypedLit:int = line.lastIndexOf( "^" );
				var isLangLit:int = line.lastIndexOf( "@" );				
				if( isTypedLit != -1 )
				{
					datatype = findDatatypeFromLastCaret( line, isTypedLit );
					return RDFTypedLiteral.instance( value, datatype );
				}
				else if( isLangLit != -1 )
				{
					lang = line.substring( isLangLit + 1 );
					return RDFLangLiteral.instance( value, lang );
				}
				else
				{
					return RDFPlainLiteral.instance( value );
				}
			}
			else
			{
				throw new Error( "N-Triple syntax error: excepted '\"' or '_:' or '<', found " + c );
			}
		}
		
		////////////////////////////////////////////////////////////////////////////////////////
		/////////////////////////////////////// Small Utils ////////////////////////////////////
		////////////////////////////////////////////////////////////////////////////////////////
		
		internal static function getIndexOfFirstNonWS( line:String ):int
		{
			for( var i:int = 0; i < line.length; i++ )
			{
				if( line.charAt( i ) != " " && line.charAt( i ) != "\t" )
					return i;
			}
			
			return i;
		}
		internal static function getIndexOfLastNonWSBeforeDot( line:String ):int
		{
			for( var i:int = line.lastIndexOf( "." ) - 1; i >= 0; i-- )
			{
				if( line.charAt( i ) != " " && line.charAt( i ) != "\t" )
					return i;
			}
			
			return i;
		}
		
		////////////////////////////////////////////////////////////////////////////////////////
		
		internal static function findIRIInBrackets( line:String ):IRI
		{
			if( line.indexOf( ">" ) == -1 )
			{
				throw new Error( "N-Triple syntax error: missing >" );
			} 
			else
			{
				return IRI.instance( line.substring( 1, line.indexOf( ">" ) ) );
			}
		}
		internal static function findDatatypeFromLastCaret( line:String, index:int ):IRI
		{
			//Remember that the char in 'index' is -supposedly- the second '^'

			if( line.charAt( index - 1 ) != "^" )
			{
				throw new Error( "N-Triple syntax error: missing second '^'" );
			}
			else if( line.charAt( index + 1 ) != "<" )
			{
				throw new Error( "N-Triple syntax error: missing '<'" );
			}
			else
			{
				return findIRIInBrackets( line.substring( line.lastIndexOf( "<" ) ) );
			}
		}
		internal static function asIRI( rawIRIString:String ):IRI
		{
			return IRI.instance( rawIRIString );
		}
		
	}
}